import { onMounted, onUnmounted, ref } from 'vue';
import { TApiFun } from './type';
import { hookResult } from './bindResult';

type IFieldNamesMap = {
  value: string;
  label: string;
  disabled?: string;
  tagProps?: string;
  index?: string;
  extra?: string;
  children?: string;
} & Record<string, string>;

type FOnChange = (e: any, extra?: any) => void;

type FSelectStreamEvent = (e: any, extra?: any) => void;

export interface IAutoSelect<D, P extends any[]> {
  leader?: boolean;
  name?: string;
  tree?: boolean;
  getValue?: () => string | number;
  immediate?: boolean;
  enhanceExtraData?: boolean;
  apiFun: TApiFun<D, P>;
  fieldNames?: IFieldNamesMap;
  transformDataFun?: (data: D) => D;
  onSuccess?: (data: D) => void;
  initQueryParams?: P;
  onChange?: FOnChange;
  // eslint-disable-next-line no-use-before-define
  upstream?: ReturnType<typeof useSelectOption>;
}

const coverMap = {
  value: 'key',
  label: 'title',
  disabled: 'disabled',
  tagProps: 'tagProps',
  index: 'index',
  extra: 'extra',
  children: 'children',
};

const covertTreeObj = (
  keys: string[],
  fieldNames: IFieldNamesMap,
  obj: any
) => {
  return keys.reduce((prev, current) => {
    // @ts-ignore
    prev[coverMap[current]] = obj[fieldNames[current]];
    return prev;
  }, {} as any);
};

const covertObj = (keys: string[], fieldNames: IFieldNamesMap, obj: any) => {
  return keys.reduce((prev, current) => {
    prev[current] = obj[fieldNames[current]];
    return prev;
  }, {} as any);
};

const defaultTransformDataFun = (
  isTree: boolean,
  data: any[],
  fieldNames: IFieldNamesMap
) => {
  const { children = '' } = fieldNames;

  const fields = Object.keys(fieldNames);

  if ((data as any).list) {
    return (data as any).list.map((e: any) => {
      return covertObj(fields, fieldNames, e);
    });
  }
  if (isTree) {
    return data.map((e) => {
      if (children && e[children] && e[children].length > 0) {
        e[children] = defaultTransformDataFun(isTree, e[children], fieldNames);
      }
      return covertTreeObj(fields, fieldNames, e);
    });
  }
  return data.map((e) => {
    return covertObj(fields, fieldNames, e);
  });
};

export const useSelectOption = <D = any, P extends any[] = any[]>(
  props: IAutoSelect<D, P>
) => {
  const {
    name = '',
    leader = false,
    apiFun,
    immediate = true,
    fieldNames,
    initQueryParams = [],
    transformDataFun,
    onSuccess,
    onChange,
    upstream,
    enhanceExtraData,
    tree = false,
    getValue,
  } = props;

  const onChangeListenerSet = new Set<FOnChange>();

  const onStreamEventListenerSet = new Set<FSelectStreamEvent>();

  const extraMap = new Map<string, any>();

  const loading = ref(false);

  const error = ref(false);

  const placeholder = ref(`请选择${name}`);

  const options = ref<D>();

  const addChangeListener = (listener: FOnChange) => {
    onChangeListenerSet.add(listener);
  };

  const removeChangeListener = (listener: FOnChange) => {
    onChangeListenerSet.delete(listener);
  };

  const addEventListener = (listener: FSelectStreamEvent) => {
    onStreamEventListenerSet.add(listener);
  };

  const removeEventListener = (listener: FSelectStreamEvent) => {
    onStreamEventListenerSet.delete(listener);
  };

  const triggerStreamEvent = () => {
    if (!getValue) {
      return;
    }
    const selfId = getValue();

    // 没有ID时，不往下层流转更新Data事件，只往下层流转更新ID事件，ID事件会触发更新Data事件，从而清空所有下层组件的Data
    // 从而避免重复更新Data事件
    if (!selfId) {
      return;
    }

    let selfExtra: any;
    if (enhanceExtraData) {
      selfExtra = extraMap.get(selfId as string);
    }

    onStreamEventListenerSet.forEach((fun) => {
      fun(selfId, selfExtra);
    });
  };

  const emitChange = (e: any) => {
    // console.log('emitChange', e);

    let extra: any;
    if (enhanceExtraData) {
      extra = extraMap.get(e);
    }
    if (onChange) {
      onChange(e, extra);
    }
    onChangeListenerSet.forEach((fun) => {
      fun(e, extra);
    });

    triggerStreamEvent();
  };

  const executeApiFun: TApiFun<D, P> = (...params: P) => {
    loading.value = true;

    // console.log(`executeApiFun:${name}`);

    return apiFun(...params)
      .then((res) => {
        let resData: any = res;
        if (transformDataFun) {
          resData = transformDataFun(res);
        }

        if (fieldNames) {
          resData = defaultTransformDataFun(tree, resData as any[], fieldNames);
        }

        if (enhanceExtraData) {
          resData.forEach((e: any) => {
            extraMap.set(e.value, e.extra);
          });
        }

        options.value = resData;
        error.value = false;
        loading.value = false;
        placeholder.value = `请选择${name}`;

        if (onSuccess) {
          onSuccess(resData);
        }

        if (leader) {
          triggerStreamEvent();
        }

        return resData;
      })
      .catch((e) => {
        // console.error('--useSelectOption--', e);
        error.value = true;
        loading.value = false;
        placeholder.value = e.message;
        throw e;
      });
  };

  const onUpStreamDataEvent = (upeId: any, extra?: any) => {
    if (!getValue) {
      throw new Error('useSelectOption:getValue is required');
    }

    executeApiFun(...([upeId, extra] as P)).then(() => {
      triggerStreamEvent();
    });
  };

  const onUpStreamChange = () => {
    emitChange('');
  };

  onMounted(() => {
    if (immediate) {
      executeApiFun(...(initQueryParams as P));
    }
    if (upstream) {
      upstream.addEventListener(onUpStreamDataEvent);
      upstream.onChange(onUpStreamChange);
    }
  });

  const retry = () => {
    executeApiFun(...(initQueryParams as P));
  };

  // const echoData = (value: string | number) => {
  //   const val = (options.value as any[]).find((e) => {
  //     return e.value === value.toString();
  //   });
  //   emitChange(val);
  // };

  onUnmounted(() => {
    onChangeListenerSet.clear();
    onStreamEventListenerSet.clear();
    if (upstream) {
      upstream.removeEventListener(onUpStreamChange);
      upstream.removeOnChange(onUpStreamChange);
    }
  });

  const setValue = (value: string | number) => {
    emitChange(value);
  };

  return hookResult(
    {
      setValue,
      retry,
      onChange: addChangeListener,
      removeOnChange: removeChangeListener,
      executeQuery: executeApiFun,
      addEventListener,
      removeEventListener,
      triggerStreamEvent,
    },
    {
      options,
      data: options,
      loading,
      error,
      placeholder,
      onChange: emitChange,
    }
  );
};
